#include "executor.h"

#define PRIVATE static

/* PRIVATE function declaration start */

PRIVATE int execMeta(db_t *db, const char *input);
PRIVATE int execSQL(db_t *db, PlanNode *plan_node);
PRIVATE int execInsert(table_t *tbl, PlanNode *insert_node);
PRIVATE table_t *execScan(db_t *db, PlanNode *scan_node, rettbl_t *rettbl);
PRIVATE int execFilter(PlanNode *filter_node, table_t *tbl, rettbl_t *rettbl);
PRIVATE int execProject(PlanNode *project_node, rettbl_t *rettbl);
PRIVATE int execDelete(table_t *tbl, rettbl_t *rettbl);
PRIVATE int execUpdate(PlanNode *update_node, table_t *tbl, rettbl_t *rettbl);

PRIVATE void execMetaExit(db_t *db);
PRIVATE void execMetaSchema();
PRIVATE void execMetaFlush(db_t *db, const char *input);
PRIVATE void execMetaLoad(db_t *db, const char *input);
PRIVATE void execMetaTree(db_t *db);

PRIVATE int execOp(const char *operand1, const char *operator, const char *operand2);
PRIVATE int checkCondition(PlanNode *filter_node, record_t *record);
PRIVATE int string2record(const char *str, record_t *record, int type);
PRIVATE int getColVal(record_t *record, const char *colref, int *ival, char *sval, int *flag);
PRIVATE int setColVal(record_t *record, const char *colref, const char *val);
PRIVATE int getField(char *buf, const char *str, int *start);
PRIVATE int setField(record_t *record, const char *buf, int idx);
PRIVATE int isNumber(const char *str);
PRIVATE char *findCharPos(const char *str, char ch, int count);
PRIVATE void formatPrint(char *data[], int rowCount, int colCount);
PRIVATE void appendstr(char *buf, char *addstr);
PRIVATE int getAttrVal(const char *row, int idx, char *buf);
PRIVATE void loadOthDB(db_t *db);

/* PRIVATE function declaration end */

int execute(db_t *db, PlanNode *plan_node, const char *input, int is_meta)
{
    if (is_meta) {
        return execMeta(db, input);
    } else {
        return execSQL(db, plan_node);
    }
}

/* PRIVATE function implemention start */

PRIVATE int execMeta(db_t *db, const char *input)
{
    if (strncmp(input, META_EXIT, strlen(META_EXIT)) == 0) {
        execMetaExit(db);
    } else if (strncmp(input, META_SCHEMA, strlen(META_SCHEMA)) == 0) {
        execMetaSchema();
    } else if (strncmp(input, META_TABLE, strlen(META_TABLE)) == 0) {

    } else if (strncmp(input, META_TABLE, strlen(META_TABLE)) == 0) {

    } else if (strncmp(input, META_TREE, strlen(META_TREE)) == 0) {
        execMetaTree(db);
    } else if (strncmp(input, META_MEM, strlen(META_MEM)) == 0) {

    } else if (strncmp(input, META_FLUSH, strlen(META_FLUSH)) == 0) {
        execMetaFlush(db, input);
    } else if (strncmp(input, META_LOAD, strlen(META_LOAD)) == 0) {
        execMetaLoad(db, input);
    } else if (strncmp(input, META_CURDB, strlen(META_CURDB)) == 0) {
        printf(":-) CURRENT DATABASE: '%s'\n", db->dbname);
    } else {
        fprintf(stderr, ":-( UNKNOWN META COMMAND: '%s'\n", input);
        return -1;
    }

    return 0;
}

PRIVATE int execSQL(db_t *db, PlanNode *plan_node)
{
    PlanNode *cur_node = plan_node;
    table_t *cur_tbl;
    rettbl_t rettbl;
    int is_filter = 0;

    initrettbl(&rettbl);

    while (cur_node != NULL) {
        switch (cur_node->type) {
            case PLAN_INSERT:
                if (execInsert(cur_tbl, cur_node) != 0) return -1;
                break;

            case PLAN_DELETE:
                if (is_filter == 0) 
                    execFilter(NULL, cur_tbl, &rettbl);
                if (execDelete(cur_tbl, &rettbl) != 0) return -1;
                break;

            case PLAN_UPDATE:
                if (is_filter == 0) 
                    execFilter(NULL, cur_tbl, &rettbl);
                if (execUpdate(cur_node, cur_tbl, &rettbl) != 0) return -1;
                break;

            case PLAN_PROJECT:
                /* 
                 * rettbl add value in exeFilter()
                 * we should add all records into rettbl if no filter
                 * set filter_node=NULL means no filtering
                 */
                if (is_filter == 0) 
                    execFilter(NULL, cur_tbl, &rettbl);
                if (execProject(cur_node, &rettbl) != 0) return -1;
                break;

            case PLAN_FILTER:
                is_filter = 1;
                if (execFilter(cur_node, cur_tbl, &rettbl) != 0) return -1;
                break;

            case PLAN_SCAN:
                if ((cur_tbl = execScan(db, cur_node, &rettbl)) == NULL) return -1;
                break;

            case PLAN_JOIN:
                break;

            default:
                return -1;
        }

        cur_node = cur_node->children[0];
    }

    destroyrettbl(&rettbl);

    return 0;
}

PRIVATE table_t *execScan(db_t *db, PlanNode *scan_node, rettbl_t *rettbl)
{
    const char *str = scan_node->value_list->value[0];

    if (strcmp(str, "user") == 0) {
        addtblref(rettbl, USER);
        return db->user;
    } else if (strcmp(str, "book") == 0) {
        addtblref(rettbl, BOOK);
        return db->book;
    } else if (strcmp(str, "borrow") == 0) {
        addtblref(rettbl, BORROW);
        return db->borrow;
    } else {
        return NULL;
    }
}

PRIVATE int execInsert(table_t *tbl, PlanNode *insert_node)
{
    char *str;
    record_t record;
    int count = tbl->record_count;

    for (int i = 0; i < insert_node->value_list->count; i++) {
        str = insert_node->value_list->value[i];
        if (string2record(str, &record, tbl->type) != 0) return -1;
        // printOneRecord(&record, record.table);
        if (insert(tbl, &record) != 0) return -1;
    }

    if (tbl->record_count - count > 0)
        printf(":-) INSERT %d ROW\n", tbl->record_count - count);

    return 0;
}

PRIVATE int string2record(const char *str, record_t *record, int type)
{
    int start = 0, field_idx = 0;
    char buf[64];

    switch (type) {
        case USER:
            record->table = USER;
            while (getField(buf, str, &start) != 1) {
                setField(record, buf, field_idx);
                field_idx++;
            }
            record->index = record->val_user.user_id;
            break;

        case BOOK:
            record->table = BOOK;
            while (getField(buf, str, &start) != 1) {
                setField(record, buf, field_idx);
                field_idx++;
            }
            record->index = record->val_book.book_id;
            break;

        case BORROW:
            record->table = BORROW;
            while (getField(buf, str, &start) != 1) {
                setField(record, buf, field_idx);
                field_idx++;
            }
            record->index = record->val_borrow.borrow_id;
            break;

        default: 
            return -1;
    }
    return 0;
}

PRIVATE int getField(char *buf, const char *str, int *start)
{
    if (str[*start] == ')')
        return 1;

    int pos = 0;
    for (size_t i = *start + 1; i < strlen(str); i++) {
        if (str[i] == ',' || str[i] == ')') {
            *start = i;
            break;
        }
        buf[pos++] = str[i];
    }
    buf[pos] = '\0';

    return 0;
}

PRIVATE int setField(record_t *record, const char *buf, int idx)
{
    switch (record->table) {
        case USER:
            if (idx == 0) {
                record->val_user.user_id = (uint16_t) atoi(buf);
            } else if (idx == 1) {
                strncpy(record->val_user.name, buf, MAX_NAME_LEN);
            } else {
                strncpy(record->val_user.email, buf, MAX_EMAIL_LEN);
            }
            break;

        case BOOK:
            if (idx == 0) {
                record->val_book.book_id = (uint16_t) atoi(buf);
            } else if (idx == 1) {
                record->val_book.quantity = (uint16_t) atoi(buf);
            } else if (idx == 2) {
                record->val_book.available = (uint16_t) atoi(buf);
            } else if (idx == 3) {
                record->val_book.category = (uint8_t) atoi(buf);
            } else if (idx == 4) {
                strncpy(record->val_book.title, buf, MAX_TITLE_LEN);
            } else {
                strncpy(record->val_book.author, buf, MAX_NAME_LEN);
            }
            break;

        case BORROW:
            if (idx == 0) {
                record->val_borrow.borrow_id = (uint16_t) atoi(buf);
            } else if (idx == 1) {
                record->val_borrow.book_id = (uint16_t) atoi(buf);
            } else if (idx == 2) {
                record->val_borrow.user_id = (uint16_t) atoi(buf);
            } else if (idx == 3) {
                if (sscanf(buf, "%hd-%hhd-%hhd", &record->val_borrow.borrow_date.year,
                                                 &record->val_borrow.borrow_date.month,
                                                 &record->val_borrow.borrow_date.day) != 3) {
                    printf("Invalid date format.\n");
                }
            } else if (idx == 4) {
                if (sscanf(buf, "%hd-%hhd-%hhd", &record->val_borrow.return_date.year,
                                                 &record->val_borrow.return_date.month,
                                                 &record->val_borrow.return_date.day) != 3) {
                    printf("Invalid date format.\n");
                }
            } else {
                record->val_borrow.status = (uint8_t) atoi(buf);
            }
            break;

        default: 
            return -1;
    }

    return 0;
}

PRIVATE int execFilter(PlanNode *filter_node, table_t *tbl, rettbl_t *rettbl)
{
    if (tbl->record_count == 0) 
        return 0;

    record_t record;

    for (int i = 0; i < tbl->record_count; i++) {
        getRecord(tbl, i, &record);
        int flag = checkCondition(filter_node, &record);
        if (flag == 0) {
            continue;   /* next record */
        } else if (flag == 1) {
            addvalue(rettbl, &record);  /* add this record */
        } else {
            return -1;  /* error */
        }
    }

    return 0;
}

/* 1 for true, 0 for false, -1 for error */
PRIVATE int checkCondition(PlanNode *filter_node, record_t *record)
{
    int retflag = 0;
    int isflag;         /* 0 - int, 1 - string */
    int or_stmt_count = 1;
    int ival;
    char sval[100];
    int pos = 0;

    if (filter_node == NULL) return 1;  /* do no need to filter */

    while (filter_node->expr_list->seperator[pos] != '\0') {
        if (filter_node->expr_list->seperator[pos] == ':')
            or_stmt_count++;
        pos++;
    }

    int idx = 0;
    for (int i = 0; i < or_stmt_count; i++) {
        int res;
        do {
            if (getColVal(record, filter_node->expr_list->column[idx], &ival, sval, &isflag) == -1) 
                return -1;
            if (isflag == 0) {  /* int */
                sprintf(sval, "%d", ival);
            }
            res = execOp(sval, filter_node->expr_list->operator[idx],
                                    filter_node->expr_list->value[idx]);

            if (res == 0) {     /* false in and statement */
                int pos = idx;
                while (filter_node->expr_list->seperator[pos] != ':' &&
                       filter_node->expr_list->seperator[pos] != '\0') 
                    pos++;
                idx = pos + 1;  /* skip the left and statement */
                break;
            }
        } while (filter_node->expr_list->seperator[idx] != ':' && 
                 filter_node->expr_list->seperator[idx++] != '\0');

        if (res == 1) {     /* true in or statement */
            retflag = 1;
            break;
        }
    }

    return retflag;
}

PRIVATE int execOp(const char *operand1, const char *operator, const char *operand2)
{
    int num1 = -1;
    int num2 = -1;

    if (isNumber(operand1)) num1 = atoi(operand1);
    if (isNumber(operand2)) num2 = atoi(operand2);

    if (num1 != -1 && num2 != -1) {
        if (strcmp(operator, ">") == 0) 
            return num1 > num2;
        else if (strcmp(operator, "<") == 0)
            return num1 < num2;
        else if (strcmp(operator, "==") == 0)
            return num1 == num2;
        else if (strcmp(operator, ">=") == 0)
            return num1 >= num2;
        else if (strcmp(operator, "<=") == 0)
            return num1 <= num2;
        else if (strcmp(operator, "!=") == 0)
            return num1 != num2;
    } else {
        if (strcmp(operator, ">") == 0) 
            return strcmp(operand1, operand2) > 0;
        else if (strcmp(operator, "<") == 0)
            return strcmp(operand1, operand2) < 0;
        else if (strcmp(operator, "==") == 0)
            return strcmp(operand1, operand2) == 0;
        else if (strcmp(operator, ">=") == 0)
            return strcmp(operand1, operand2) >= 0;
        else if (strcmp(operator, "<=") == 0)
            return strcmp(operand1, operand2) <= 0;
        else if (strcmp(operator, "!=") == 0)
            return strcmp(operand1, operand2) != 0;
    }

    return 0;
}

PRIVATE int isNumber(const char *str)
{
    const char *p = str;
    while (*p) {
        if (!isdigit(*p)) {
            return 0;
        }
        p++;
    }
    return 1;
}

PRIVATE int getColVal(record_t *record, const char *colref, int *ival, char *sval, int *flag)
{
    switch (record->table) {
        case USER:
            if (strcmp(colref, "user_id") == 0) {
                *flag = 0;  /* int */
                *ival = record->val_user.user_id;
            } else if (strcmp(colref, "name") == 0) {
                *flag = 1;  /* string */
                strncpy(sval, record->val_user.name, MAX_NAME_LEN);
                sval[MAX_NAME_LEN] = '\0';
            } else if (strcmp(colref, "email") == 0) {
                *flag = 1;  /* string */
                strncpy(sval, record->val_user.email, MAX_EMAIL_LEN);
                sval[MAX_EMAIL_LEN] = '\0';
            } else {
                fprintf(stderr, ":-( unknown colref: '%s'\n", colref);
                return -1;
            }
            break;

        case BORROW:
            if (strcmp(colref, "borrow_id") == 0) {
                *flag = 0;
                *ival = record->val_borrow.borrow_id;
            } else if (strcmp(colref, "book_id") == 0) {
                *flag = 0;
                *ival = record->val_borrow.book_id;
            } else if (strcmp(colref, "user_id") == 0) {
                *flag = 0;
                *ival = record->val_borrow.user_id;
            } else if (strcmp(colref, "borrow_date") == 0) {
                *flag = 1;
                snprintf(sval, 20, "%hd-%hhd-%hhd", 
                    record->val_borrow.borrow_date.year,
                    record->val_borrow.borrow_date.month,
                    record->val_borrow.borrow_date.day);
            } else if (strcmp(colref, "return_date") == 0) {
                *flag = 1;
                snprintf(sval, 20, "%hd-%hhd-%hhd", 
                    record->val_borrow.return_date.year,
                    record->val_borrow.return_date.month,
                    record->val_borrow.return_date.day);
            } else if (strcmp(colref, "status") == 0) {
                *flag = 0;
                *ival = record->val_borrow.status;
            } else {
                fprintf(stderr, "unknown colref: '%s'\n", colref);
                return -1;
            }
            break;

        case BOOK:
            if (strcmp(colref, "book_id") == 0) {
                *flag = 0;
                *ival = record->val_book.book_id;
            } else if (strcmp(colref, "quantity") == 0) {
                *flag = 0;
                *ival = record->val_book.quantity;
            } else if (strcmp(colref, "available") == 0) {
                *flag = 0;
                *ival = record->val_book.available;
            } else if (strcmp(colref, "category") == 0) {
                *flag = 0;
                *ival = record->val_book.category;
            } else if (strcmp(colref, "title") == 0) {
                *flag = 1;  /* string */
                strncpy(sval, record->val_book.title, MAX_TITLE_LEN);
                sval[MAX_TITLE_LEN] = '\0';
            } else if (strcmp(colref, "author") == 0) {
                *flag = 1;  /* string */
                strncpy(sval, record->val_book.author, MAX_NAME_LEN);
                sval[MAX_NAME_LEN] = '\0';
            } else {
                fprintf(stderr, "unknown colref: '%s'\n", colref);
                return -1;
            }
            break;

        default:
            return -1;
    }

    return 0;
}

PRIVATE int execProject(PlanNode *project_node, rettbl_t *rettbl)
{
    int proj_idx[20];   /* project column index */
    int proj_count = 0;
    for (int i = 0; i < 20; i++) 
        proj_idx[i] = -1;
    char **data = (char **) malloc(sizeof(char *) * (rettbl->valnum + 1));    /* project data */

    /* add the project columns to table header and project indexs */
    if (strcmp(project_node->value_list->value[0], "*") == 0) {
        data[0] = (char *) malloc(30 * rettbl->colnum);     /* table header */
        data[0][0] = '\0';
        for (int i = 0; i < rettbl->colnum; i++) {
            appendstr(data[0], rettbl->colrefs[i]);
            if (i != rettbl->colnum - 1)
                appendstr(data[0], ";");    /* seperator */
            proj_idx[i] = i;
        }
        proj_count = rettbl->colnum;
    } else {
        data[0] = (char *) malloc(30 * project_node->value_list->count);     /* table header */
        data[0][0] = '\0';
        for (int i = 0; i < project_node->value_list->count; i++) {
            for (int j = 0; j < rettbl->colnum; j++) {
                if (strcmp(project_node->value_list->value[i], rettbl->colrefs[j]) == 0) {
                    appendstr(data[0], rettbl->colrefs[j]);
                    if (i != project_node->value_list->count - 1)
                        appendstr(data[0], ";");    /* seperator */
                    proj_idx[i] = j;
                    break;
                }
            }
        }
        proj_count = project_node->value_list->count;
    }

    char buf[100];  /* store each line data */
    for (int i = 0; i < rettbl->valnum; i++) {
        int pos = 0;
        for (int j = 0; j < proj_count; j++) {
            int len = getAttrVal(rettbl->values[i], proj_idx[j], &(buf[pos]));
            pos += len;

            if (j != proj_count - 1) {
               buf[pos++] = ';';
            }
        }
        buf[pos] = '\0';
        data[i + 1] = (char *) malloc(MAX_WIDTH);
        data[i + 1][0] = '\0';
        appendstr(data[i + 1], buf);
        // printf("%s\n", buf);
    }
    formatPrint(data, rettbl->valnum + 1, proj_count);

    /* free the data */
    for (int i = 0; i < rettbl->valnum + 1; i++)
        free(data[i]);
    free(data); 
    data = NULL;

    return 0;
}

PRIVATE char *findCharPos(const char *str, char ch, int count)
{
    const char *p = str;
    for (int i = 0; i < count; i++) {
        p = strchr(p, ch);
        if (p == NULL) 
            return NULL;
        p++;
    }

    return (char *) p;
}

PRIVATE int execDelete(table_t *tbl, rettbl_t *rettbl)
{
    int idx;
    char buf[10];
    for (int i = 0; i < rettbl->valnum; i++) {
        char *start = rettbl->values[i];
        char *end = findCharPos(start, ';', 1);
        int len = end - start - 1;
        memcpy(buf, start, len);
        buf[len] = '\0';

        idx = atoi(buf);
        delete(tbl, idx);
    }    

    if (rettbl->valnum > 0)
        printf(":-) DELETE %d ROW\n", rettbl->valnum);

    return 0;
}

PRIVATE int execUpdate(PlanNode *update_node, table_t *tbl, rettbl_t *rettbl)
{
    int idx;
    char buf[10];
    record_t record;

    for (int i = 0; i < rettbl->valnum; i++) {
        char *start = rettbl->values[i];
        char *end = findCharPos(start, ';', 1);
        int len = end - start - 1;
        memcpy(buf, start, len);
        buf[len] = '\0';

        idx = atoi(buf);
        search(tbl, idx, &record);

        /* update the record */
        for (int k = 0; k < update_node->expr_list->count; k++) {
            if (setColVal(&record, update_node->expr_list->column[k], update_node->expr_list->value[k]) != 0)
                return -1;
            if (modify(tbl, idx, &record) != 0)
                return -1;
        }
    }    

    if (rettbl->valnum > 0)
    printf(":-) UPDATE %d ROW\n", rettbl->valnum);

    return 0;
}

PRIVATE int setColVal(record_t *record, const char *colref, const char *val)
{
    switch (record->table) {
        case USER:
            if (strcmp(colref, "user_id") == 0) {
                record->val_user.user_id = atoi(val);
            } else if (strcmp(colref, "name") == 0) {
                strncpy(record->val_user.name, val, MAX_NAME_LEN);
                record->val_user.name[MAX_NAME_LEN - 1] = '\0';
            } else if (strcmp(colref, "email") == 0) {
                strncpy(record->val_user.email, val, MAX_EMAIL_LEN);
                record->val_user.email[MAX_EMAIL_LEN - 1] = '\0';
            } else {
                fprintf(stderr, ":-( unknown colref: '%s'\n", colref);
                return -1;
            }
            break;

        case BORROW:
            if (strcmp(colref, "borrow_id") == 0) {
                record->val_borrow.borrow_id = atoi(val);
            } else if (strcmp(colref, "book_id") == 0) {
                record->val_borrow.book_id = atoi(val);
            } else if (strcmp(colref, "user_id") == 0) {
                record->val_borrow.user_id = atoi(val);
            } else if (strcmp(colref, "borrow_date") == 0) {
                if (sscanf(val, "%hd-%hhd-%hhd", &record->val_borrow.borrow_date.year,
                                                 &record->val_borrow.borrow_date.month,
                                                 &record->val_borrow.borrow_date.day) != 3)
                    printf("Invalid date format.\n");
            } else if (strcmp(colref, "return_date") == 0) {
                if (sscanf(val, "%hd-%hhd-%hhd", &record->val_borrow.return_date.year,
                                                 &record->val_borrow.return_date.month,
                                                 &record->val_borrow.return_date.day) != 3)
                    printf("Invalid date format.\n");
            } else if (strcmp(colref, "status") == 0) {
                record->val_borrow.status = atoi(val);
            } else {
                fprintf(stderr, "unknown colref: '%s'\n", colref);
                return -1;
            }
            break;

        case BOOK:
            if (strcmp(colref, "book_id") == 0) {
                record->val_book.book_id = atoi(val);
            } else if (strcmp(colref, "quantity") == 0) {
                record->val_book.quantity = atoi(val);
            } else if (strcmp(colref, "available") == 0) {
                record->val_book.available = atoi(val);
            } else if (strcmp(colref, "category") == 0) {
                record->val_book.category = atoi(val);
            } else if (strcmp(colref, "title") == 0) {
                strncpy(record->val_book.title, val, MAX_TITLE_LEN);
                record->val_book.title[MAX_TITLE_LEN - 1] = '\0';
            } else if (strcmp(colref, "author") == 0) {
                strncpy(record->val_book.author, val, MAX_NAME_LEN);
                record->val_book.author[MAX_NAME_LEN - 1] = '\0';
            } else {
                fprintf(stderr, "unknown colref: '%s'\n", colref);
                return -1;
            }
            break;

        default:
            return -1;
    }

    return 0;
}

PRIVATE void formatPrint(char *data[], int rowCount, int colCount)
{
    int maxWidths[MAX_COLS] = {0};

    /* find the max width among columns */
    for (int i = 0; i < rowCount; i++) {
        for (int j = 0; j < colCount; j++) {
            char *start = findCharPos(data[i], ';', j);
            char *end = findCharPos(start, ';', 1);
            int len = (end != NULL) ? (int) (end - start - 1) :
                (int) (strlen(data[i]) - (start - data[i]));
            if (len > maxWidths[j]) {
                maxWidths[j] = len;
            }
        }
    }

    /* print seperator */
    for (int j = 0; j < colCount; j++) {
        printf("+");
        for (int i = 0; i < maxWidths[j] + 2; i++) {
            printf("-");
        }
    }
    printf("+\n");

    /* print table header */
    char buf[50];
    for (int j = 0; j < colCount; j++) {
        getAttrVal(data[0], j, buf);
        printf("| %-*s ", maxWidths[j], buf);
    }
    printf("|\n");

    /* print seperator */
    for (int j = 0; j < colCount; j++) {
        printf("+");
        for (int i = 0; i < maxWidths[j] + 2; i++) {
            printf("-");
        }
    }
    printf("+\n");

    /* print the data */
    for (int i = 1; i < rowCount; i++) {
        for (int j = 0; j < colCount; j++) {
            getAttrVal(data[i], j, buf);
            printf("| %-*s ", maxWidths[j], buf);
        }
        printf("|\n");
    }

    /* print seperator */
    for (int j = 0; j < colCount; j++) {
        printf("+");
        for (int i = 0; i < maxWidths[j] + 2; i++) {
            printf("-");
        }
    }
    printf("+\n");
}

PRIVATE void appendstr(char *buf, char *addstr)
{
    /* assume there is no buffer overflow */
    size_t buf_len = strlen(buf);
    strcpy(buf + buf_len, addstr);    
}

PRIVATE int getAttrVal(const char *row, int idx, char *buf)
{
    char *start = findCharPos(row, ';', idx);
    char *end = findCharPos(start, ';', 1);
    int len = (end != NULL) ? (int) (end - start - 1) :
        (int) (strlen(row) - (start - row));
    memcpy(buf, start, len);
    buf[len] = '\0';

    return len;
}

PRIVATE void loadOthDB(db_t *db)
{
    if (db->user->record_count != 0) {
        destroyTable(db->user);
        db->user = createTable(USER);
    }

    if (db->book->record_count != 0) {
        destroyTable(db->book);
        db->book = createTable(BOOK);
    }

    if (db->borrow->record_count != 0) {
        destroyTable(db->borrow);
        db->borrow = createTable(BORROW);
    }
}

PRIVATE void execMetaExit(db_t *db)
{
    printf(":-) EXIT...\n");
    destroyDB(db);
    exit(EXIT_SUCCESS);
}

PRIVATE void execMetaSchema()
{
    printf(
        "==================================\n"
        "user   ** PRIMARY KEY: user_id **\n"
        "          user_id      INTEGER\n"
        "          name         VARCHAR(%d)\n"
        "          email        VARCHAR(%d)\n\n"
        "book   ** PRIMARY KEY: book_id **\n"
        "          book_id      INTEGER\n"
        "          quantity     INTEGER\n"
        "          available    INTEGER\n"
        "          category     INTEGER\n"
        "          title        VARCHAR(%d)\n"
        "          author       VARCHAR(%d)\n\n"
        "borrow ** PRIMARY KEY: borrow_id **\n"
        "          borrow_id    INTEGER\n"
        "          book_id      INTEGER\n"
        "          user_id      INTEGER\n"
        "          borrow_date  DATE\n"
        "          return_date  DATE\n"
        "          status       INTEGER\n"
        "==================================\n",
        MAX_NAME_LEN, MAX_EMAIL_LEN, MAX_TITLE_LEN, MAX_NAME_LEN
    );
}

PRIVATE void execMetaFlush(db_t *db, const char *input)
{
    char filename[MAX_DBNAME_LEN];
    char *start = findCharPos(input, ' ', 1);
    if (start == NULL) {
        snprintf(filename, MAX_DBNAME_LEN, "%s", db->dbname);
    } else {
        char *end = findCharPos(start, ' ', 1);
        int len = (end != NULL) ? (int) (end - start - 1) :
            (int) (strlen(input) - (start - input));
        memcpy(filename, start, len); filename[len] = '\0';
        memcpy(db->dbname, filename, len); db->dbname[len] = '\0';
    }

    if (flushToDisk(db, filename) == 0)
        printf(":-) FLUSH DATABASE: '%s'\n", filename);
}

PRIVATE void execMetaLoad(db_t *db, const char *input)
{
    loadOthDB(db);

    char filename[80];
    char *start = findCharPos(input, ' ', 1);
    if (start == NULL) {
        fprintf(stderr, "Error: .load filename\n");
        return;
    }
    char *end = findCharPos(start, ' ', 1);
    int len = (end != NULL) ? (int) (end - start - 1) :
        (int) (strlen(input) - (start - input));
    memcpy(filename, start, len); filename[len] = '\0';
    memcpy(db->dbname, filename, len); db->dbname[len] = '\0';

    if (loadFromDisk(db, filename) == 0)
        printf(":-) LOAD DATABASE: '%s'\n", filename);
}

PRIVATE void execMetaTree(db_t *db)
{
    printf("\n");
    printf("****** USER   TREE ******\n");    
    Travel(db->user->tree);
    printf("****** BOOK   TREE ******\n");    
    Travel(db->book->tree);
    printf("****** BORROW TREE ******\n");    
    Travel(db->borrow->tree);
}

/* PRIVATE function implemention end */